//     __ _____ _____ _____
//  __|  |   __|     |   | |  JSON for Modern C++
// |  |  |__   |  |  | | | |  version 3.11.3
// |_____|_____|_____|_|___|  https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT

#pragma once

#include <cstddef>   // size_t
#include <iterator>  // input_iterator_tag
#include <string>    // string, to_string
#include <tuple>     // tuple_size, get, tuple_element
#include <utility>   // move

#if JSON_HAS_RANGES
#include <ranges>  // enable_borrowed_range
#endif

#include <nlohmann/detail/abi_macros.hpp>
#include <nlohmann/detail/meta/type_traits.hpp>
#include <nlohmann/detail/value_t.hpp>

NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail {

template <typename string_type>
void int_to_string(string_type &target, std::size_t value) {
  // For ADL
  using std::to_string;
  target = to_string(value);
}
template <typename IteratorType>
class iteration_proxy_value {
 public:
  using difference_type = std::ptrdiff_t;
  using value_type = iteration_proxy_value;
  using pointer = value_type *;
  using reference = value_type &;
  using iterator_category = std::input_iterator_tag;
  using string_type = typename std::remove_cv<typename std::remove_reference<
      decltype(std::declval<IteratorType>().key())>::type>::type;

 private:
  /// the iterator
  IteratorType anchor{};
  /// an index for arrays (used to create key names)
  std::size_t array_index = 0;
  /// last stringified array index
  mutable std::size_t array_index_last = 0;
  /// a string representation of the array index
  mutable string_type array_index_str = "0";
  /// an empty string (to return a reference for primitive values)
  string_type empty_str{};

 public:
  explicit iteration_proxy_value() = default;
  explicit iteration_proxy_value(
      IteratorType it,
      std::size_t array_index_ =
          0) noexcept(std::is_nothrow_move_constructible<IteratorType>::value &&
                      std::is_nothrow_default_constructible<string_type>::value)
      : anchor(std::move(it)), array_index(array_index_) {}

  iteration_proxy_value(iteration_proxy_value const &) = default;
  iteration_proxy_value &operator=(iteration_proxy_value const &) = default;
  // older GCCs are a bit fussy and require explicit noexcept specifiers on
  // defaulted functions
  iteration_proxy_value(iteration_proxy_value &&) noexcept(
      std::is_nothrow_move_constructible<IteratorType>::value &&
      std::is_nothrow_move_constructible<string_type>::value) =
      default;  // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor,cppcoreguidelines-noexcept-move-operations)
  iteration_proxy_value &operator=(iteration_proxy_value &&) noexcept(
      std::is_nothrow_move_assignable<IteratorType>::value &&
      std::is_nothrow_move_assignable<string_type>::value) =
      default;  // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor,cppcoreguidelines-noexcept-move-operations)
  ~iteration_proxy_value() = default;

  /// dereference operator (needed for range-based for)
  const iteration_proxy_value &operator*() const { return *this; }

  /// increment operator (needed for range-based for)
  iteration_proxy_value &operator++() {
    ++anchor;
    ++array_index;

    return *this;
  }

  iteration_proxy_value operator++(int) &  // NOLINT(cert-dcl21-cpp)
  {
    auto tmp = iteration_proxy_value(anchor, array_index);
    ++anchor;
    ++array_index;
    return tmp;
  }

  /// equality operator (needed for InputIterator)
  bool operator==(const iteration_proxy_value &o) const {
    return anchor == o.anchor;
  }

  /// inequality operator (needed for range-based for)
  bool operator!=(const iteration_proxy_value &o) const {
    return anchor != o.anchor;
  }

  /// return key of the iterator
  const string_type &key() const {
    JSON_ASSERT(anchor.m_object != nullptr);

    switch (anchor.m_object->type()) {
    // use integer array index as key
    case value_t::array: {
      if (array_index != array_index_last) {
        int_to_string(array_index_str, array_index);
        array_index_last = array_index;
      }
      return array_index_str;
    }

    // use key from the object
    case value_t::object:
      return anchor.key();

    // use an empty key for all primitive types
    case value_t::null:
    case value_t::string:
    case value_t::boolean:
    case value_t::number_integer:
    case value_t::number_unsigned:
    case value_t::number_float:
    case value_t::binary:
    case value_t::discarded:
    default:
      return empty_str;
    }
  }

  /// return value of the iterator
  typename IteratorType::reference value() const { return anchor.value(); }
};

/// proxy class for the items() function
template <typename IteratorType>
class iteration_proxy {
 private:
  /// the container to iterate
  typename IteratorType::pointer container = nullptr;

 public:
  explicit iteration_proxy() = default;

  /// construct iteration proxy from a container
  explicit iteration_proxy(typename IteratorType::reference cont) noexcept
      : container(&cont) {}

  iteration_proxy(iteration_proxy const &) = default;
  iteration_proxy &operator=(iteration_proxy const &) = default;
  iteration_proxy(iteration_proxy &&) noexcept = default;
  iteration_proxy &operator=(iteration_proxy &&) noexcept = default;
  ~iteration_proxy() = default;

  /// return iterator begin (needed for range-based for)
  iteration_proxy_value<IteratorType> begin() const noexcept {
    return iteration_proxy_value<IteratorType>(container->begin());
  }

  /// return iterator end (needed for range-based for)
  iteration_proxy_value<IteratorType> end() const noexcept {
    return iteration_proxy_value<IteratorType>(container->end());
  }
};

// Structured Bindings Support
// For further reference see https://blog.tartanllama.xyz/structured-bindings/
// And see https://github.com/nlohmann/json/pull/1391
template <std::size_t N, typename IteratorType, enable_if_t<N == 0, int> = 0>
auto get(const nlohmann::detail::iteration_proxy_value<IteratorType> &i)
    -> decltype(i.key()) {
  return i.key();
}
// Structured Bindings Support
// For further reference see https://blog.tartanllama.xyz/structured-bindings/
// And see https://github.com/nlohmann/json/pull/1391
template <std::size_t N, typename IteratorType, enable_if_t<N == 1, int> = 0>
auto get(const nlohmann::detail::iteration_proxy_value<IteratorType> &i)
    -> decltype(i.value()) {
  return i.value();
}

}  // namespace detail
NLOHMANN_JSON_NAMESPACE_END

// The Addition to the STD Namespace is required to add
// Structured Bindings Support to the iteration_proxy_value class
// For further reference see https://blog.tartanllama.xyz/structured-bindings/
// And see https://github.com/nlohmann/json/pull/1391
namespace std {

#if defined(__clang__)
// Fix: https://github.com/nlohmann/json/issues/1401
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wmismatched-tags"
#endif
template <typename IteratorType>
class tuple_size<::nlohmann::detail::iteration_proxy_value<
    IteratorType>>  // NOLINT(cert-dcl58-cpp)
    : public std::integral_constant<std::size_t, 2> {};

template <std::size_t N, typename IteratorType>
class tuple_element<N, ::nlohmann::detail::iteration_proxy_value<
                           IteratorType>>  // NOLINT(cert-dcl58-cpp)
{
 public:
  using type = decltype(get<N>(
      std::declval<::nlohmann::detail::iteration_proxy_value<IteratorType>>()));
};
#if defined(__clang__)
#pragma clang diagnostic pop
#endif

}  // namespace std

#if JSON_HAS_RANGES
template <typename IteratorType>
inline constexpr bool ::std::ranges::enable_borrowed_range<
    ::nlohmann::detail::iteration_proxy<IteratorType>> = true;
#endif
